package wx

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"

	"github.com/zeromicro/go-zero/core/stores/redis"
)

type Code2Session struct {
	OpenId     string `json:"openid"`
	SessionKey string `json:"session_key"`
	UnionId    string `json:"unionid"`
	ErrCode    int64  `json:"errcode"`
	ErrMsg     string `json:"errmsg"`
}

// GetCode2Session 登录凭证校验
func GetCode2Session(code string, appId string, appSecret string) (*Code2Session, error) {
	url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
		appId, appSecret, code)
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var c2s Code2Session
	err = json.Unmarshal(body, &c2s)
	if err != nil {
		return nil, err
	}

	return &c2s, nil
}

type AccessToken struct {
	AccessToken string
	ExpiresIn   int64
}
type TokenResult struct {
	AccessToken string `json:"access_token"`
	ExpiresIn   int64  `json:"expires_in"`
}

// GetAccessToken 获取微信访问token
func GetAccessToken(appId, appSecret, accessTokenCacheKey string, client *redis.Redis) (*AccessToken, error) {
	wxAccessTokenKey := fmt.Sprintf("wx:access_token:%s", accessTokenCacheKey)
	if client != nil {
		token, err := getTokenFromRedis(client, wxAccessTokenKey)
		if err != nil {
			return nil, err
		}

		if token != nil {
			return &AccessToken{
				AccessToken: *token,
			}, nil
		}
	}

	tr, err := getTokenDirect(appId, appSecret)
	if err != nil {
		return nil, err
	}

	if client != nil {
		err = client.Hmset(wxAccessTokenKey, map[string]string{
			"access_token": tr.AccessToken,
			"expires_in":   strconv.Itoa(int(tr.ExpiresIn)),
			"updated_at":   strconv.Itoa(int(time.Now().Unix())),
		})
		if err != nil {
			return nil, err
		}
	}

	return &AccessToken{
		AccessToken: tr.AccessToken,
		ExpiresIn:   tr.ExpiresIn,
	}, nil
}

func getTokenFromRedis(client *redis.Redis, tokenKey string) (token *string, err error) {
	result, err := client.Hgetall(tokenKey)
	if err != nil {
		return
	}

	if len(result) != 0 {
		updateAtUnix, err := strconv.Atoi(result["updated_at"])
		if err != nil {
			return nil, err
		}

		t := time.Unix(int64(updateAtUnix), 0)
		duration, err := strconv.Atoi(result["expires_in"])
		if err != nil {
			return nil, err
		}

		tmp := result["access_token"]
		token = &tmp
		t = t.Add(time.Duration(duration) * time.Second)

		if t.After(time.Now()) {
			return token, nil
		}
	}

	return nil, nil
}

func getTokenDirect(appId, appSecret string) (*TokenResult, error) {
	url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret)
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}

	defer func() {
		_ = resp.Body.Close()
	}()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var tr TokenResult
	err = json.Unmarshal(body, &tr)
	if err != nil {
		return nil, err
	}

	return &tr, nil
}
